/*
 * @Author       : Bing Chen chenbing@iocharger.com
 * @Date         : 2025-01-21 16:09:28
 * @LastEditors  : Bing Chen chenbing@iocharger.com
 * @LastEditTime : 2025-03-15 13:06:45
 * @FilePath     : \openchaoji-corelib\x86\app_hal_x86.h
 * @Description  :
 *
 * Copyright (c) 2025 OpenChaoJi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the icense for the specific language governing permissions and
 * limitations under the License.
 *
 */
#ifndef __APP_HAL_X86_H__
#define __APP_HAL_X86_H__

/*
定义x86模拟需要的evcc/secc通信共同结构和方法
*/

#include "../shared/include/OpenChaoJi_shared.h"
#include <sys/socket.h> // For socket functions
#include <netinet/in.h> // For sockaddr_in
#include <unistd.h> // For close
#include <fcntl.h> // For fcntl
#include <errno.h> // For errno
#include <arpa/inet.h>

// 单行打印, 用于定位持续调用的函数
#define HAL_DEBUG_LINE  LOG_DEBUG("%s", __FUNCTION__);

/* 模拟组合   EVCC      SECC
     0枪    A类系统     A类系统
     1枪    B类系统     B类系统
     2枪    B类系统     A类系统 (用B类的EVCC通过适配器连接A类的SECC, EVCC配置S2p)
     3枪    B类系统     A类系统 (用B类的EVCC通过适配器连接A类的SECC, EVCC未配置S2p)
 */
#define HAL_SIMU_CONNECTOR_NUM      2       // 暂时先不测试适配器

/**
 * 用UDP模拟CAN报文, 每个报文13字节, 前4字节是can id, 第5字节是dlc, 后8字节是data
 *
 * SECC使用29730端口开始, EVCC使用18487端口开始, 按照枪号分配端口
 * 例如 1枪的SECC使用29731端口, EVCC使用18488端口
 *
 * 模拟测试时, EVCC和SECC都使用127.0.0.1, 实际可以分开在两个机器(不同IP)上运行
 *
 * 利用特殊can id为导引部分evcc和secc同步开关状态, 从而同步检测点电压状态
 *
 */
#define HAL_UDP_PORT_SECC_START     27930
#define HAL_UDP_PORT_EVCC_START     18487
#define HAL_UDP_ADDR_SECC           "127.0.0.1"
#define HAL_UDP_ADDR_EVCC           "127.0.0.1"

#define HAL_UDP_PACKET_LEN          13      // 用最大13字节模拟CAN报文, 前4字节是can id, 第5字节是dlc, 后8字节是data
#define HAL_UDP_PACKET_QUEUE_SIZE   20      // 缓冲队列大小, 一般20个足够了

/**
 * 模拟CAN报文接收缓冲队列
 */
typedef struct {
    struct can_pdu queue[HAL_UDP_PACKET_QUEUE_SIZE];
    int front;
    int rear;
    int count;
} fifo_queue;
void fifo_init(fifo_queue *q);
int fifo_is_full(fifo_queue *q);
int fifo_is_empty(fifo_queue *q);
int fifo_enqueue(fifo_queue *q, struct can_pdu *pdu);
int fifo_dequeue(fifo_queue *q, struct can_pdu *pdu);

/**
 * 模拟CAN的UDP Socket
 */
int hal_create_udp_socket(uint16_t port);
void hal_init_dest_socket_addr(const char * addr, uint16_t port, struct sockaddr_in *dest_addr);

/**
 * 每次检查是否有UDP(CAN)报文, 如有则填入缓冲队列, 模拟底层异步读取CAN包
 *
 * 如果遇到用于同步开关状态的特殊can id, 则调用同步函数, 传入同步数据
 */
typedef void (*can_data_pilot_sync_func)(OpenChaoJi_hal_handle handle, struct can_pdu * can_recv_info);

int hal_try_read_can_pdu(OpenChaoJi_hal_handle handle, int socket_fd, fifo_queue * recv_queue, can_data_pilot_sync_func pilot_sync_func, struct can_pdu * can_recv_info);

int hal_send_can_pdu(OpenChaoJi_hal_handle handle, int socket_fd, struct sockaddr_in *dest_addr, const struct can_pdu * can_send_info);

/**
 * 利用特殊can id为导引部分evcc和secc同步电压状态
 */
#define CAN_ID_PILOT_SYNC   0x01020304

/**
 * 导引报文, 用于同步evcc和secc的开关状态
 * evcc和secc各自检查和刷新自己的开关状态, 并将状态写入报文, 发送给对方
 */
#define HA_SIMU_PILOT_A_A       0   // EVCC-A类系统, SECC-A类系统
#define HA_SIMU_PILOT_B_B       1   // EVCC-B类系统, SECC-B类系统
#define HA_SIMU_PILOT_B_A       2   // EVCC-B类系统, SECC-A类系统 (使用适配器)

struct can_data_pilot_sync {

    uint8_t pilot_type;                 // 模拟导引类型

    // evcc开关字节
    uint8_t evcc_plug           : 1;    // 插枪状态, 由evcc_start时候触发模拟插枪过程, secc被动
    uint8_t evcc_pe_lost        : 1;    // evcc侧PE是否短线
    // evcc开关字节(A类系统专用, B类系统默认都为断开)
    uint8_t evcc_S              : 1;      // A类系统半插合模拟, B类系统默认为断开
    // evcc开关字节(B类系统专用, A类系统默认都为断开)
    uint8_t evcc_S2             : 1;    // evcc的开关S2状态 （初始化状态为断开）
    uint8_t evcc_S2p            : 1;    // evcc的开关S2'状态 （选配, 如配置则初始化状态为断开）
    uint8_t evcc_Sv             : 1;    // evcc的开关Sv状态 （初始化状态由制造商自定义）
    uint8_t evcc_dc             : 1;
    uint8_t evcc_lock           : 1;

    uint8_t evcc_emergency      : 1;
    uint8_t evcc_dummy1         : 7;

    // secc开关字节
    uint8_t secc_pe_lost        : 1;    // secc侧PE是否短线
    // secc开关字节(B类系统专用, A类系统默认都为断开)
    uint8_t secc_S0             : 1;    // secc的开关S0状态 S0[选配 , 在鉴权（如刷卡）后闭合；如不具备则等同于常闭状态 , 本附录以 S0为 常闭为例]
    uint8_t secc_S1             : 1;    // secc的开关S1状态 （初始化状态为断开）
    // secc开关字节(普通非导引)
    uint8_t secc_dc             : 1;
    uint8_t secc_ac             : 1;
    uint8_t secc_aux            : 1;
    uint8_t secc_lock           : 1;
    uint8_t secc_emergency      : 1;

    uint16_t evcc_battery_voltage;  // evcc电池电压, 单位0.1V
    uint16_t secc_output_power;     // secc输出功率, 单位0.01kW
};
void hal_log_pilot_sync(struct can_data_pilot_sync *sync);

/**
 * 根据当前的开关状态判断当前系统类型, Utility层使用电压判断, HAL模拟则使用开关组合状态来判断, 用于得到对应的电压给Utility层
 */
enum OpenChaoJi_pilot_status_gbt18487_a hal_pilot_status_gbt18487_a(struct can_data_pilot_sync *sync);
enum OpenChaoJi_pilot_status_gbt18487_b hal_pilot_status_gbt18487_b(struct can_data_pilot_sync *sync);

/**
 * 根据当前的开关状态判断三个检测点电压
 */
#define INVALID_VOLTAGE 15.0f
// 检测点1, SECC端
float hal_pilot_voltage_secc_checkpoint_1(struct can_data_pilot_sync *sync);
// 检测点2, EVCC端
float hal_pilot_voltage_evcc_checkpoint_2(struct can_data_pilot_sync *sync);
// 检测点3, EVCC端, 仅B类系统
float hal_pilot_voltage_evcc_checkpoint_3(struct can_data_pilot_sync *sync);

/**
 * 模拟测试时, 模拟继电器, 支持延时闭合和断开
 */
struct hal_x86_relay
{
    const char * name;          // 继电器名称

    uint32_t delay_ms_close;    // 继电器闭合延时(即发起闭合命令到实际闭合, 用于测试延迟动作)
    uint32_t delay_ms_open;     // 继电器断开延时(即发起断开命令到实际断开, 用于测试延迟动作)

    uint8_t close;              // 继电器当前设置状态, 1-闭合, 0-断开
    uint32_t close_ticks;       // 继电器当前状态的设置时间, 毫秒, 加上delay_ms_open或delay_ms_close即为实际状态
};

void hal_x86_relay_init(struct hal_x86_relay * relay, const char * name, uint32_t delayms_close, uint32_t delayms_open, uint8_t close);
void hal_x86_relay_set(int idx, struct hal_x86_relay * relay, int close);
void hal_x86_relay_service(int idx, struct hal_x86_relay * relay);
uint8_t hal_x86_relay_get(int idx, struct hal_x86_relay * relay);


#endif